'''
The module from which all the main simulation functionalities are
carried out.

@author: Hatem Oraby
'''

from twistedSim.simSetup import installTime
installTime() #This has to be first thing made before any other component
              #is loaded.

import sys
import os
import time
import heapq
import copy

from twistedSim.Scheduler import SchedulerSingleton as scheduler
from twistedSim.override._time import SimTimeSingleton
from twistedSim.override._socket import IpInterface, _MachineNetworkMixin

class Platforms:
    '''
    An enumeration that holds different types of OS.
    '''
    winVista = 0
    unix = 50

class Machine(_MachineNetworkMixin):
    '''
    Represents and hold information about a machine instance.
    Applications (represented by Module class) are hosted and run
    under a machine instance (as in real life). A Machine instance
    can host one or more Module instances.
   
    L{IpInterface} instances can be attached to a machine instance.
    By default, each machine has a localhost IpInterface instance. 
   
    @type machineName: C{str}
    @param machineName: The name assigned to the machine. The
        name will be used to identify the machine on the networks that
        the machine is part of. It'll later be used in debugging
        information.
        machineName is half-dummy.
       
    @type platform: L{Platforms}
    @param platform: A value coming from L{Platforms} class, It'll
        emulate some of platform features (to the modules and virtual
        path) according on the machine platform.
        platform is currently dummy.
   
    @type rootDir: C{str}
    @param rootDir: The directory will act as the root directory for
        virtual path of the L{Module} instances hosted on the machine.
        This variable is currently dummy.
   
    @type current: L{Machine} or C{None}
    @cvar current: C{None} if no module is running or reference
        to the L{Machine} instance hosting the currently running
        module.
   
    @type machineInstances: C{list}
    @cvar machineInstances: A list of all created machines
        instances.
       
    @type _id: C{int}
    @cvar _id: Used to derive the ids for the new machines.
    '''
   
    current = None #Refers to the currently active machine
    _id = 0 #Id of the current machine
   
    def __init__(self, machineName = None,
                 platform = sys.platform, rootDir = None):
       
        self._machineName = machineName   
        self._id = Machine._id
        Machine._id += 1
        self._moduleId = 1 #An id for created modules
        self._modules = {}
        _MachineNetworkMixin.__init__(self)

    def registerModule(self, module):
        '''
        Register a module with the machine and returns the module
        id the module on that machine.
       
        This method shouldn't be called by user, it's rather
        called by the module loader to attach the module to the
        machine only on loading time and not before that.
       
        @type module: L{Module}
        @param module: The module to be registered with the machine.
       
        @rtype: C{int}
        @return: The id of the module on the machine.
        '''
        self._moduleId += 1
        self._modules[self._moduleId] = module
        self._modulesAddrs[self._moduleId] = {}
        from twistedSim.override._socket import SocketUtil
        for sktType in SocketUtil.SocketsSupported:
            self._modulesAddrs[self._moduleId][sktType] = []
        return self._moduleId
   
    def getModules(self):
        '''
        Return all the module currently hosted on the machine.
       
        @rtype: C{list} of L{Module}
        @return: List of module instances hosted on the machine.
        '''
        return self._modules.values()
       
    def getMachineName(self):
        '''
        Return the machine name.
       
        @rtype: C{None} or C{str}
        @return: The machine name or None if a name wasn't
            associated with the machine.
        '''
        return self._machineName
   

    def removeModule(self, module):
        '''Remove a module from machine
        User shouldn't use this to remove a module, the user should
        rather use L{Module.die}() instead.
       
        @type module: L{Module}
        @param module: The module to be removed.
        '''
        moduleId = module.getId()
        try:
            del self._modules[moduleId]
        except KeyError:
            raise ValueError("Module doesn't belong to the machine.") 
                    
        for sktType in self._modulesAddrs[moduleId].keys():
            for ip, port in self._modulesAddrs[moduleId][sktType]:
                del self._skts[ip][sktType][port]
        del self._modulesAddrs[moduleId]

    
    def killMachine(self):
        '''Kill the machine: Remove all the modules running on the
        machine and release all the L{IpInterface} associated with the
        machine.
        The method in not implemented yet.
        '''
        raise NotImplementedError("killMachine is not implemented yet")
        for module in self._modules.values():
            self.removeModule(module)

    def _setCurrentMachine(MachineObj):
        Machine.current = MachineObj
    _setCurrentMachine = staticmethod(_setCurrentMachine)

class FileType:
    '''
    An enumeration holding different files types (It may
    be transfered out of this module later).
    '''
    sourceFile = 0
    dll = 1       


class Module:
    '''
    Represents and hold information about an application instance.
    A Module instance must be hosted under a Machine
    instance.
   
    @type moduleName: C{str}
    @param moduleName: The name of the module file name.
   
    @type handler: L{interfaces.moduleHandler} interface implementation
    @param handler: The handler that will manage the way
        module will be loaded and it's context switching operations.

   
    @type modulePath: C{str}
    @param modulePath: The path at which the module exist.
   
    @type fileType: A L{FileType} attribute or C{None}.
    @param fileType: The file type of module, a C{None} value
        indicates to use default behavior.
        fileType is currently dummy and accepts only value of None.
   
    @type sysArgs: C{str}
    @param sysArgs: The string that will be be parsed and passed
        to the module as sys.args. The first element of sys.args
        is automatically generated, the first element is the
        module path and name.
   
    @type startTime: C{float} or C{None}
    @param startTime: The time from current time (timeout) at
        which the module will start executing. If C{None} is
        provided (default) then the timeout = 0 is used (i.e. Now).

   
    @type killTime: C{float} or C{None}
    @param killTime: The time at which the module will forcefully
        be stopped (if it didn't finish execution yet). A C{None}
        value indicates not to kill.
        killTime is currently dummy.
   
    @type stdIn: C{file}
    @param stdIn: The file that the input stream will be read from.
        The default value will use the file object found in
        C{sys.stdin}.
        stdIn is currently dummy.

    @type stdOut: C{file}
    @param stdOut: A file that the output stream will be redirected
        to. The default value will use the file object found in
        C{sys.stdout}.
        stdOut is currently dummy.
 
    @type stdErr: C{file}
    @param stdErr:  A file that the error stream will be redirected
        to. The default value will use the file object found in
        C{sys.stderr}.
        stdErr is currently dummy.
   
    @type virtualPath: C{str}
    @param virtualPath: A string of a parh. It doesn't have to be
        an existing path but have to be a valid path name.
        The path will be used as the execution path on the module
        on the hosting machine.
        The hosting machine will memorize the files that the hosted
        modules running on it created. The modules running on the
        same machine can access each other files.
        virtualPath is currently dummy.
   
    @type nickName: C{str} or C{None}
    @param nickName: The nickname to be associated with the
        module. It will be used for to ease the debugging process.
        If C{None} (default) is provided then the moduleName argument
        will be used.
       
    @type time: C{float}
    @ivar time: The variable will store the timeout of next event
        of scheduled in the module.
   
    @type curdir: C{str}
    @ivar curdir: A string that will hold the os.curdir of the
        module, the variable shouldn't be manipulated by user
        rather it will be used by the context switching components.
        The initial value of curdir will be equal to the modulePath
   
    @type dead: C{bool}
    @ivar dead: A flag that signals whether the module is active or
    stopped working.
   
    @type isProcessBased: C{bool}
    @cvar isProcessBased: A constant that indicates whether this class
        handles the applications in a new process C(True) or if all the
        applications are created in the same process (False).
   
    @type current: L{Module} or C{None}
    @cvar current: In run time, the module will reference the
        currently active Module, if none is running the variable
        will have C{None} value.
    '''
    isProcessBased = False
    current = None #This will reference the current active module
    installStd = True
   
    def getCurrentModule():
        '''Return the current active module
       
        @rtype: L{Module} or C{None}
        @return: The module that is currently active or C{None} if no
            module is currently active.
        '''
        return Module.current
    getCurrentModule = staticmethod(getCurrentModule)
   
    def __init__(#Module Loading attributes
             self,moduleName, handler, modulePath = None,
             fileType = None, sysArgs = "" ,
             #Module Timing attributes
             startTime = None, killTime = None,
             #Streams attributes
             stdIn = sys.stdin, stdOut = sys.stdout, stdErr = sys.stderr,
             #Environment variables
             virtualPath = None,
             #Debugging attributes
             nickName = None):       
        #-Module Loading attributes:
        #Set Module fileName
        self._moduleName = moduleName
        #Set module path, if module path = None then refer to our
        # current path (we could use the __file__ attribute).
        if modulePath == None:
                modulePath = str(os.path.realpath("."))
        elif not os.path.isdir(modulePath):
            raise "Simulation: Invalid Path"
        self._modulePath = modulePath
        self.curdir = copy.copy(modulePath)#Copying str is useless,is it??
       
        #Set sys args
        if type(sysArgs) != str:
            raise TypeError("Simulation: SysArgs isn't of type string")
        sysArgs = sysArgs.split(" ")
        #Add the first argument which is the full file name
        sysArgs = [str(modulePath)+"/"+str(moduleName)] + sysArgs
        self._sysArgs = sysArgs
        if fileType != None:
            raise "Simulation: Currently only source files are supported"
        self._fileType = fileType
       
        #-Module Timing attributes:       
        #Set start time of the module
        self._startTime = startTime
        #set time to bring the module down
        if killTime != None:
            raise "Simulation: Module delayed killed isn't" + \
                  " currently supported"
        self._killTIme = killTime
       
        #-Simulation behavior attributes:       
        #Set the class that should handle the module behavior.
        self._handler = handler
        self._handler.setModule(self)
              
        #-Streams attributes:
        if stdIn != sys.stdin:
            raise "Simulation: Streams redirection isn't" + \
                  " currently supported."
        self._stdIn = stdIn
        if stdOut != sys.stdout:
            raise "Simulation: Streams redirection isn't" + \
                  " currently supported."
        self._stdOut = stdOut
        if stdErr != sys.stderr:
            raise "Simulation: Streams redirection isn't" + \
                  " currently supported."
        self._stdErr = stdErr
       
        #-Environment variables:
        #This is the path that the module will think that he is running in on
        # the machine underlying machine.
        if virtualPath != None:
            raise "Simulation: virtual path isn't supported yet."
        self._virtualPath = virtualPath
       
        #-Debugging attributes:
        #This name will be used to facilitate debugging
        if nickName == None:
            nickName = moduleName
        self._nickName = nickName
       
        #-Other attributes:
        # Some of the attributes will move to other places out of this
        # class.
        #The fileDescripotors associated with this module
        self._fileno = {}
        self._filenoID = 1
        #This should hold the statement that exists after
        # reactor.run() statement.
        self._afterRun = None
        #This will hold sys.modules that this module owns.      
        self._sysModules = None #It'll be equal copy(sys.modules) but we
                                # don't want it to use memory unless it's 
                                # running, so this attribute will be 
                                # assigned in simManager._addModuleNow()       
        self._owner = None #Machine we are running under
        #The attribute will mark if the module will need to be run
        self._markForImmediateSchedule = False
        self._externalEventsTime = [] #Time of external events like network
                                      # events, this variable used to track
                                      # such events.
        #The attribute stores the time of reported occurrence of next event
        # on that module. A None value donates that there isn't any 
        # scheduled event on that module.
        self.time = None
        self._isTimeout = True #Whether the stored time is a timeout or
                               # external event
        #A flag to whether this module is active or died
        self.dead = False
        self._timeout = None #Used to track the time of the next occurring
                             # event.
        self._event = None #Use to reference this application's event in 
                           # the scheduler

    
    def getAttrs(self):
        '''
        Returns a dictionary that contains all the attributes of
        the module.
       
        Instead of making million getters and setters, a
        dictionary is returned with all the module attributes
        (only few get their get & set due to their frequent usage).
       
        @rtype: C{dict}
        @return: The attributes of the module returned in a
            dictionary. The name of the keys are the same as
            L{Module.__init__} constructor arguments names.
        '''
        attrs = {}
        attrs['moduleName'] = self._moduleName
        attrs['modulePath'] = self._modulePath
        attrs['fileType'] = self._fileType
        attrs['sysArgs'] = self._sysArgs
        attrs['startTime'] = self._startTime
        attrs['killTime'] = self._killTIme
        attrs['moduleHandler'] = self._handler
        attrs['stdIn'] = self._stdIn
        attrs['stdOut'] = self._stdOut
        attrs['stdErr'] = self._stdErr
        attrs['virutalPath'] = self._virtualPath
        attrs['nickName'] = self._nickName
        attrs['id'] = self._id   
        return attrs
   

    def getId(self):
        '''Return the module ID on the machine it's running on.
        It's the caller responsibility not to call the method until it's
        registered with a machine.'''
        return self._id
       
    def setOwner(self, machine):
        '''
        Associate the module with owner. The method shouldn't
        called by user but rather called by the module loader.
       
        On registering with the machine, the module acquire
        it's id on that machine.
       
        @type machine: L{Machine}
        @param machine: The machine instance that the
            module will register itself with.
        '''
        self._owner = machine
        self._id = machine.registerModule(self)
       
    def getOwner(self):
        '''
        Returns the machine hosting the module.
       
        @rtype: L{Machine} or C{None}
        @return: The machine instance hosting the current module
            or C{None} if it's not hosted yet.
        '''
        return self._owner

    def getSysArgs(self):
        '''
        Return the sys.args associated with the module.
       
        @rtype: C{str}
        @return: The module sysArgs.
        '''
        return self._sysArgs
   
    def die(self):
        '''
        The module is stopped gracefully, i.e. all its connected sockets
        are disconnected with disconnect message rather than timeout.
        '''
        if self.dead:
            print "Module " + self._moduleName + " is already dead."
        else:
            if self.isRunning():
                machine = self.getOwner()
                machine.removeModule(self)
            if self.time != None:
                #print "Time now:    " + repr(time.time())
                #print "Module Time: " + repr(self.time)
                try:
                    scheduler.removeEvent(self.time, self._event)
                except (KeyError,ValueError), e:
                    #I may be in the same cycle so it wasn't
                    #assigned to None yet.
                    pass
                self.time = None
            self.dead = True
            #Create debug options for verbosity
            #print "Module " + self._nickName + " died."
            
            socket = self._sysModules['socket']
            for fileDesc in self._fileno:
                if type(fileDesc) == socket.socket:
                    fileDesc.close()
            del self._fileno #Delete every reference
            self._owner = None

    def createFileDesc(self, fileObject):
        '''
        Associate a file object with the module and return a file descriptor
        for it. This method shouldn't be called by the user, it is rather 
        called by the emulated sockets method.
        
        The method doesn't check for error.
       
        @type fileObject: C{object}
        @param fileObject: The object itself.
        
        @rtype fileno: C{int}
        @return fileno: The file descriptor.
       
        '''
        fileNo = self._filenoID
        self._filenoID += 1
        self._fileno[fileNo] = fileObject
        return fileNo
       
    def getFileDesc(self, fileno):
        '''
        Returns the object associated with a file descriptor.
       
        @type fileDesc: C{fileno}
        @param fileDesc: The file descriptor to be retrieved.
       
        @rtype: C{object}
        @return: The file object.
        '''
        return self._fileno[fileno]

    def isFileDescExists(self, fileno):
        '''
        Returns whether or not a file descriptor exists or not.
        
        @rtype: C{True|False}
        @return: True or False.
        '''
        return fileno in self._fileno

    def removeFileDesc(self, fileno):
        '''
        Remove a file descriptor association with the module.
       
        @type fileno: C{int}
        @param fileno: The file descriptor to be removed.
        '''
        del self._fileno[fileno]
       
    def runAsEvent(self):
        '''
        DON't CALL THIS METHOD TO START RUNNING THE MODULE,
        call L{SimManager.addModule} instead.
       
        The method is called when a module turn has come to run.
        '''
        if self.dead:#Don't waste time with dead modules
            return
        self._handler.preSwitchContext()
        self._switchContext()
        self._handler.postSwitchContext()
        #If this module was flagged to be run then let's
        # put the flag down.
        self.ImmediateScheduleDown()
        self._event = None
        if self.installStd:
            installStdStreams(self._nickName)
        #Make the actual run
        self._handler.runUntilCurrent()
        timeout = self._handler.timeout()
        if self.installStd:
            uninstallStdStreams()
        if timeout == -1:
            #Declare the guy dead gracefully, i.e. The guy
            #closed the sockets he's connected to by himself.
            self.die()
        else:
            self._timeout = timeout
            self._fixSchd()                    
        self._handler.preSwitchOriginal()
        self._switchOriginal()
        self._handler.postSwitchOriginal()
        Machine.current = None #It makes debugging easier, we
        Module.current = None  # don't want a machine to refer
                               # to other by mistake.

    def _fixSchd(self):
        self._schedule(self._timeout, isTimeout = True)

    def schedule(self, schTime):
        self._schedule(schTime)
           
    def _schedule(self, schTime, isTimeout = False):
        '''I need to differentiate between timeout (i.e. internal event)
        and external events handing '''
        #TODO: Find a neater algorithm, this one is a total mess.
        #Check if my old time refers to an already executed event.
        # if yes then I've already executed that event and now I can
        # temporarily set the self.time to a nothing until I find out
        # throughout this function if I have something to execute or
        # not.       
        if self.time <= time.time():
            #print "0 ",
            if self.time in self._externalEventsTime:
            #    print "a ",
                self._externalEventsTime.remove(self.time)
            self.time = None
            #if self.time ==  None means that nothing scheduled for me yet.

        #The next hack is an actually bad hack that messes out the timing
        # and make it wrong. I'm leaving it so that no one would think to
        # improve the algorithm by doing it. I'm saving your breath.
        #if self._markForImmediateSchedule:
        #    return

        if schTime != None and schTime + time.time() == self.time:
            return

        if isTimeout:
            #print "1 ",
            if schTime != None:
                #print "a ",
                #Check if an event is already scheduled and if its a network
                # event then throw it forward.
                if self.time != None:
                    #print "i ",
                    if schTime + time.time() < self.time  :
                        #If the scheduled event time is less than the next
                        # event time then remove the next event from the scheduler
                        # and schedule the new event.
                        if not self._isTimeout:
                            #print "x ",
                            #If the scheduled event time is less than the next
                            # event time then and its a network event then store
                            # that self.time event and remember to execute it
                            # later by storing it in self._externalEventsTime
                            heapq.heappush(self._externalEventsTime, self.time)
                    #elif schTime + time.time() > self.time and self._isTimeout:
                    #    #Though that the time of this event is more than our
                    #    # next event time however because its an internal event
                    #    # which is reported by the reactor so the reactor has
                    #    # simply deleted the currently stored event and is
                    #    # telling us about his new next stored event's timeout.
                    #    pass #Actually we don't do anything here cuz the next
                    #         # few lines outside of this if do what we want
                    try:
                        scheduler.removeEvent(self.time, self._event)
                    except ValueError:
                        #The best way to handle error is to mute them :S.
                        # I really don't why this happen but it doesn't 
                        # mess the algorithm.
                        pass
                    self._event = scheduler.enqueue(schTime, self.runAsEvent)
                    self.time = schTime + time.time()
                    self._isTimeout = True
                else: #self.time = None
                    #print "J ",
                    if self._externalEventsTime and \
                       self._externalEventsTime[0] < schTime + time.time():
                        #print "x ",
                        #The time of the next network event is less than the
                        # sch time (i.e. less than the next stored event time),
                        # in such case we take the the first event in the event
                        # list and set it as our next self.time event.
                        self.time = heapq.heappop(self._externalEventsTime)
                        self._event = scheduler.enqueue(self.time - time.time(),
                                                        self.runAsEvent)
                        self._isTimeout = False
                        #We won't schedule the incoming event in
                        # self._externalEventsTime as its an internal event.
                    else:
                        #print "y ",
                        self._event = scheduler.enqueue(schTime, self.runAsEvent)
                        self.time = schTime + time.time()
                        self._isTimeout = True
                   
            else:
                #print "b ",
                #Here the application's event-loop (i.e. reactor) don't have any
                # future stored events, so if we have waiting network events
                # in the future then we should schedule ourself to run in the
                # future.
                if self._externalEventsTime:
                    #print "i ",
                    self.time = heapq.heappop(self._externalEventsTime)
                    self._event = scheduler.enqueue(self.time - time.time(),
                                                    self.runAsEvent)
                    self._isTimeout = False
                else:
                    #print "J ",
                    #The reactor got no further events stored and there are
                    # forseen network events in our self._externalEventsTime.
                    self.time = None
                    self._isTimeout = False
        else:# no timeout, i.e. a network event.
            #print "2 ",
            if self.time != None:
                #print "a ",
                #SchTime will always has a value here and won't be a None
                if schTime == None:
                    raise "schTime == None is not expected here."
                #If the time of execution of the next external event
                # is less than the time first event in the list.
                if schTime + time.time() < self.time:
                    #print "i ",
                    #Anyway remove the currently stored event from
                    # the scheduler.
                    try:
                        scheduler.removeEvent(self.time, self._event)
                    except ValueError:
                        #Very bad hack, I dunno why its sometimes is not there.
                        pass
                    if not self._isTimeout:
                        #print "x ",
                        #The currently stored event is a network or external event
                        # and hence the reactor won't detect it so we need to
                        # schedule it ourselves to run in the future so we place
                        # it in self._externalEventsTime
                        heapq.heappush(self._externalEventsTime, self.time)
                    #Now since the incoming time is the least event time then
                    # schedule ourselves to run in the future
                    self._event = scheduler.enqueue(schTime, self.runAsEvent)
                    self.time = schTime + time.time()
                    self._isTimeout = False
                else:
                    #print "J ",
                    #Its a network event but its execution time is more than the
                    # the time of the next scheduled run in self.time, therefore
                    # we will just store in self._externalEventsTime to be run
                    # in the future.
                    heapq.heappush(self._externalEventsTime,
                                   schTime + time.time())
            else: #self.time == None
                #print "b ",
                #I don't know yet if the schtime will be the first to be execute
                # events or there are previous network events that needs to be
                # handled.
                #Here the application's event-loop (i.e. reactor) don't have any
                # future stored events, so if we have waiting network events
                # in the future then we should schedule ourself to run in the
                # future.
                if not self._externalEventsTime or (self._externalEventsTime and\
                    schTime + time.time() < self._externalEventsTime[0]):
                    #print "i ",
                    self._event = scheduler.enqueue(schTime, self.runAsEvent)
                    self.time = schTime + time.time()
                    self._isTimeout = False
                else:
                    #print "J ",
                    #Here the new time of the new scheduled event is less than
                    # the time of the first event of the previously stored
                    # network event in self._externalEventsTime so we take
                    # the first event in self._externalEventsTime as our next in
                    # event and store the currently incoming event in schTime in
                    # self._externalEventsTime so that it would be executed when
                    # its time come.
                    heapq.heappush(self._externalEventsTime,
                                   schTime + time.time())
                    self.time = heapq.heappop(self._externalEventsTime)
                    self._event = scheduler.enqueue(self.time - time.time(),
                                                    self.runAsEvent)
                    self._isTimeout = False

    def ImmediateScheduleRaise(self):
        self.schedule(0.000000001)
        self._markForImmediateSchedule = True
   
    def ImmediateScheduleDown(self):
        self._markForImmediateSchedule = False

    def _switchContext(self, create = False):
        '''
        The method responsible to switch "View" from the real module
        running the simulation to the "View" of the module about to
        run.
       
        The data of the moudle hosting simulation should be stored as
        attributes in L{_originalData} class.
        '''
        global sys
        _originalData.sysPath = copy.copy(sys.path)
        _originalData.sysModules = copy.copy(sys.modules)
        _originalData.curdir = copy.copy(os.getcwd())
        _originalData.sysArgs = sys.argv
        if create:
            self._modulesReplace2Create()            
        self._modulesReplace3(self._sysModules)
        
        import sys
        sys.argv = self.getSysArgs()
        sys.path += [self._modulePath]
        os.chdir(self.curdir)
        Module.current = self
        Machine.current = self.getOwner()
   
   
    def _switchOriginal(self, create = False):
        '''
        The method is responsible to swtich back from the "View" of
        emulated running module to the "view" of the real module
        hosting the simulation process.
       
        The data of the moudle hosting simulation should be get from
        attributes in L{_originalData} class.
        '''   
        self._sysModules = copy.copy(sys.modules)
        self._modulesReplace3(_originalData.sysModules)
       
        os.chdir(_originalData.curdir)
        sys.argv = _originalData.sysArgs
        sys.path = _originalData.sysPath
        
        Module.current = None #That for ensuring no conflict occur 
        Machine.current = None# and ease the dubgging process.
   
    def _modulesReplace3(self, src):
        temp_dict = src.copy()
        sys.modules.clear()
        sys.modules.update(src)
        src.clear()
        src.update(temp_dict)


    def _modulesReplace2Create(self):
        from sys import modules
        sysModules = {}
        sysModules['sys'] = sys
        #sysModules["__builtin__"] = __builtins__
        sysModules["twistedSim.overrideManager"] = \
                                    modules["twistedSim.overrideManager"]
        sysModules['twistedSim'] = modules['twistedSim']
        sysModules['twistedSim.override'] = modules['twistedSim.override']
        sysModules["os"] = modules["os"] 
        sysModules["os.path"] = modules["os.path"]
        sysModules["twistedSim.simManager"] = modules["twistedSim.simManager"]
        self._sysModules = sysModules
        
                   
    def load(self, machine):
        '''
        If time has come for the module to be loadedthen this
        method is called. This method do the real processing.
               
        @type machine: L{Machine}
        @param machine: The machine that module will be hosted on and run on.
        '''
        self.setOwner(machine)
        
        self._handler.preSwitchContext(True)
        self._switchContext(True)
        self._handler.postSwitchContext(True)
        try:
            from imp import load_source
            self._handler.preLoad()
            attrs = self.getAttrs()
            if self.installStd:
                installStdStreams(self._nickName)
            fullName = self._modulePath + "/" + self._moduleName + ".py"
            loader = load_source("__main__",fullName)
            if self.installStd:
                uninstallStdStreams()
            #Switch back to the real main
            self._handler.postLoad()
        #Enable this if you want to mute errors that occurs on loading
        # your application.
        #except Exception,e: 
        #    raise e
        #    print "Something Wrong happened, file wasn't loaded"
        #    print str(type(e)) +"   : "+ str(e)
        finally:
            self._switchOriginal(True)

    def isRunning(self):
        #Note that if I'm not dead I might have not start
        # running yet, A bad hack to check if I didn't start
        # running (until we set proper variables) is to check
        # if I have an owning machine yet or not, if not then
        # I'm not running yet.
        return not self.dead and self.getOwner() != None
       
    def getNickname(self):
        '''
        Returns the nickname which is associated with the application.
        An applicaation's nickname is specified through the nickName
        optional argument in this classes' constructor. If no nickName
        is specified then the application's entry module is used as the
        nickname of the application.
        '''
        return self._nickName

    def getStartTime(self):
        '''
        Return the start time (or rather timeout) at which the module is 
        scheduled to run after.
        '''
        return self._startTime


    def _setCurrentModule(ModuleObj):
        '''
        Shouldn't be called by user.
        '''
        Module.current = ModuleObj
    _setCurrentModule = staticmethod(_setCurrentModule)

def installStdStreams(nickName):
    global sys
    from overrideManager import OverrideManager
    OverrideManager.storeEntity(sys.stdout, "sys.stdout")
    OverrideManager.storeEntity(sys.stderr, "sys.stderr")
    
    sys.stdout = overrideStdOut()#Thats lots of processing
    sys.stderr = overrideStdErr()
    import sys #Bad hack, but I don't know why it doesn't work at first place
    sys.stdout = overrideStdOut()
    sys.stderr = overrideStdErr()
    
    #Set nickname
    overrideStdOut.nickName = nickName
    overrideStdErr.nickName = nickName

def uninstallStdStreams():
    import sys
    try:
        sys.stdout.flushText()
        sys.stderr.flushText()
    except:
        raise "uninstallStd is called before installStd()"
    from overrideManager import OverrideManager
    sys.stdout = OverrideManager.getEntity("sys.stdout")
    sys.stderr = OverrideManager.getEntity("sys.stderr")
    overrideStdOut.nickName = "TwistedSim"
    overrideStdErr.nickName = "TwistedSim"

class overrideStdOut():
    '''
    Override the Stdout stream with a modified stream that prints out data
    differently (prints data with extra debugging information).
    '''
    #This is a quick junkie hack, proper up later
    nickName = "Simulator"
    buffer = ""
   
    def __init__(self):
        pass
   
    def _emit(self, text):
        string =  overrideStdOut.nickName + ": " + text
        from overrideManager import OverrideManager
        stdout = OverrideManager.getEntity("sys.stdout")
        if stdout == None:
            sys.stdout.write(string + "\n")
        else:
            stdout.write(string + "\n")
    
    def write(self, text,mode = 'r', buffer = 0):
        #I don't know what the default value of buffer
        overrideStdOut.buffer += text
        index = overrideStdOut.buffer.find("\n")
        while index != -1:
            string = overrideStdOut.buffer[0:index]
            self._emit(string)
            overrideStdOut.buffer = overrideStdOut.buffer[index+1:]
            index = overrideStdOut.buffer.find("\n")
        
    def flushText(self):
        if overrideStdOut.buffer:
            self._emit(overrideStdOut.buffer)
            overrideStdOut.buffer = ""

    def __getattr__(self, name):
        from overrideManager import OverrideManager
        stdout = OverrideManager.getEntity("sys.stdout")
        if stdout != None:
            return getattr(stdout, name)
        else:
            #import sys
            return getattr(sys.stdout, name)



class overrideStdErr():
    '''
    Override the Stderr stream with a modified stream that prints out data
    differently (prints data with extra debugging information).
    '''
    nickName = "Simulator"
    buffer = ""
   
    def __init__(self):
        pass
   

    def _emit(self, text):
        string =  overrideStdErr.nickName + ": " + text
        from overrideManager import OverrideManager
        stderr = OverrideManager.getEntity("sys.stderr")
        if stderr == None:
            sys.stderr.write(string + "\n")
        else:
            stderr.write(string + "\n")
    
    def write(self, text,mode = 'r', buffer = 0):
        #I don't know what the default value of buffer
        overrideStdErr.buffer += text
        index = overrideStdErr.buffer.find("\n")
        while index != -1:
            string = overrideStdErr.buffer[0:index]
            self._emit(string)
            overrideStdErr.buffer = overrideStdErr.buffer[index+1:]
            index = overrideStdErr.buffer.find("\n")
        
    def flushText(self):
        if overrideStdErr.buffer:
            self._emit(overrideStdErr.buffer)
            overrideStdErr.buffer = ""
   
    def __getattr__(self, name):
        from overrideManager import OverrideManager
        stderr = OverrideManager.getEntity("sys.stderr")
        if stderr != None:
            return getattr(stderr, name)#__getattr__ causes problem
                                        # when called by logger,
                                        #Replaced by getattr()
        else:
            return getattr(sys.stderr, name)


IpInterface = IpInterface #Has no effect, just declaring that
                          # the user can refer to this as part
                          # of the Module and not just an import. 

class _originalData:
    '''
    Holds the machine hosting the simulation real data while a
    module is running.
    '''
    sysArgs = None
    sysModules = {} #This will be filled by context switching
    path = os.getcwd()
    class storage:
        mainModule = sys.modules["__main__"]
    ''' When using switchContext3, Without the previous statement,
    our main module (the original script main module not the loaded ones)
    loses all it's attributes values and the attribute value become None.
    
    For example, consider that our main module has the following function:
    #
    def calledLater():
    ____print "Var1: " + var1
 
    var1 = object()
    simManager.callLater(100,calledLater)
    #
    Now that results in None, and if you tried to dir and print gloabls()
    of the module you'll get:   
    Key: __builtins__     Value: <module '__builtin__' (built-in)>
    Key: __file__         Value: None
    Key: simManager       Value: None
    Key: __name__         Value: None
    Key: __doc__          Value: None
    
    With exception for the builtins module, all other modules had None value
    associate with them , I dunno why but the possible reason that i can
    think of (which can be totally wrong) is that the when we switch between
    modules using switchContext and create new modules, the garbage collector
    (or some other entity) doesn't realize that we have reference to the old
    modules, so when we come back again the objects aren't there and a value
    of None exists instead, sure there tons of flaws with this explanation
    but i really have no clue about what's going on there.
    
    Anyway, it seems that when we keep reference here to the main module
    things work right, which brings another question, is the main module the
    only vulnerable module or there other modules that could be vulnerable as
    well, and is there a pattern so we could automate the rescue process for
    all the modules instead of rescuing only the main module???
   
    just for sake of documentation, another potential reason is that this
    problem appears with module functions that are bound to the module.
   
    This is written later Later: I've decided to rescue all modules and copy
    the content of sys.modules here through a for loop that sets all the
    setattribute of _originaldata to entries of sys.modules, anyway that makes
    the __main__ module copied twice but i left this copy as a documentaiton
    about why i did that.
    '''
   
    if "pydevd_frame" in sys.modules: #A hack for pydev as it blows up
                                      #for also some strange reason
                                      #like the mainModule mentioned
                                      #aboive, it did work to some exntent
        #pydevdFrame = sys.modules["pydevd_frame"]
        #pydevd = sys.modules["pydevd"]
        #pudevd_comm = sys.modules["pydevd_comm"]
        #urllib = sys.modules["urllib"]
        #urlparse = sys.modules["urlparse"]
        #site = sys.modules["site"]
        '''It's being replaced with the for loop below'''
        pass
for entry in sys.modules.keys():#This part is to avoid unncessary evil
                                #that occurs with context switching and
                                #and with debuggers (and god knows what else).
    setattr(_originalData.storage, entry, sys.modules[entry])#Heavy load??, maybe
'''This for loop is to avoid the problem that i faced with problem documented
at L{_originalData.mainModule}'''


class SimManager():
    '''
    The class manages the simulation process. It contains crucial
    functionalities needed to load modules and to control simulation
    process.
    '''

    stop = False

    def __init__(self):
        self._preRun = []
        self._postRun = []
   
    def run(self, endTime = None):
        '''
        Start running the simulation process.
       
        @type endTime: C{float}
        @param endTime: The timeout from the start of the simulation
            at which the simulation will be forced to stop even if
            it didn't finish yet. A value of None will indicate
            to simulate until simulation ends.
            This variable is currently dummy.
        '''
       
        for method, args, kargs in self._preRun:
            method(*args,**kargs)
       
        count = 1 #used for debugging
        while not SimManager.stop: #Simulation loop
            SimManager._setGlobalTime() #Advance simulation time to time
                                        # of nearest event time.
           
            if count: #Used in debugging
                #print "Time: " + str(time.asctime())
                count += 1
                #print count
            if count == 3858: #Used to debug a certain step
                pass
           
            events = scheduler.popNearestEvents()
            #print "Events: " + str(events)
            for event in events: #Run each event in the current events slot
                if hasattr(event, 'run'): #It's a callable
                    event.run()
                else:
                    print event
                    raise "Unknown type scheduled, expected Event"
       
            if scheduler.isEmpty():
                SimManager.stop = True
            #End of simulation Loop
   
        for method, args, kargs in self._postRun:
            method(*args,**kargs)   
        print "Simulation done."

    def addPreRun(self, f, *args, **kargs):
        '''Add a method to be executed before simulation starts.
       
        @type f: C{callable}
        @param f: The callable to call.
       
        @type args: C{list}
        @param args: A list of arguments to pass as parameters to the
        callable when it's called.
       
        @type kargs: C{dict}
        @param kargs: A dictionary who's keys are string that exist as
        callable optional parameter, and the values is the value to be
        passed to that parameter.
        '''
        self._preRun.append((f,args,kargs))
   
    def addPostRun(self, f, *args, **kargs):
        '''Add a method to be executed after simulation finish.
       
        @type f: C{callable}
        @param f: The callable to call.
       
        @type args: C{List}
        @param args: A list of arguments to pass as parameters to the
        callable when it's called.
       
        @type kargs: C{dict}
        @param kargs: A dictionary who's keys are string that exist as
        callable optioanl parameter, and the values is the value to be
        passed to that parameter.
        '''
        self._postRun.append((f,args,kargs))
   
    def _setGlobalTime(self):
        '''Advance time to the nearest event time'''
        if not scheduler.isEmpty():
            nearestTime = scheduler.nearestEventTime()
            SimTimeSingleton.setTime(nearestTime)
  
    def callLater(self, time, f, *args, **kargs):
        '''A callLater function for the simulator, it takes the time
        from now to call the callable after, and it takes the callable
        itself and optional set of arguments to be passed to the
        callable.
       
        @type time: C{float}
        @param time: The time from now in seconds (simulation time) to
        call the callable after.
       
        @type f: C{callable}
        @param f: The callable to call.
       
        @type args: C{List}
        @param args: A list of arguments to pass as parameters to the
        callable when it's called.
       
        @type kargs: C{dict}
        @param kargs: A dictionary who's keys are string that exist as
        callable optioanl parameter, and the values is the value to be
        passed to that parameter.
        '''
        scheduler.enqueue(time, f, *args, **kargs)

    def addModule(self, module, machine):
        '''
        Associate a module with a machine and adds it to the
        simulation process.
       
        The adding process doesn't happen here, this method rather
        schedule the module to be added in it's start time.
       
        @type module: L{Module}
        @param module:  The module instance to be added.
       
        @type machine: L{Machine}
        @param machine: The machine instance that the module will
            hosted and run on.
        '''
        startTime = module.getStartTime()
       
        if startTime == None:
            startTime = 0
        scheduler.enqueue(startTime,module.load, *[machine])

SimManager = SimManager()
